```
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}
\hypersetup{%
    pdfborder = {0 0 0}
}

\lhead{\bfseries SEED Labs -- 防火墙绕过实验}
\newcommand{\tunnelFigs}{./Figs}


\newcommand{\pointupright}[1]{\ding{218} \textbf{\texttt{#1}}}

\newcommand{\externalnet}{\texttt{10.8.0.0/24}\xspace}
\newcommand{\internalnet}{\texttt{192.168.20.0/24}\xspace}
\newcommand{\hostA}{\texttt{10.8.0.99}\xspace}
\newcommand{\hostB}{\texttt{192.168.20.99}\xspace}


\usepackage{hyperref}

\begin{document}


\begin{center}
{\LARGE 防火墙绕过实验}
\end{center}

\seedlabcopyright{2022}



% *******************************************
% SECTION
% ******************************************* 
\section{概述}

在某些情况下，防火墙设置过于严格，给用户带来不便。例如，许多公司和学校会实施出口过滤策略，阻止网络内部的用户访问特定网站或互联网服务，如游戏和社交网络站点。绕过防火墙的方法有很多种。一种常见的方法是使用隧道技术，隐藏真实目的。建立隧道的方法有很多。
最常用的两种隧道技术是虚拟专用网络（VPN）和端口转发。
本实验的目标是帮助学生获得这两项技术的实际操作经验。本实验涵盖以下主题：

\begin{itemize}[noitemsep]
\item 防火墙绕过
\item 虚拟专用网络（VPN）
\item 端口转发
\item SSH隧道 
\end{itemize}

\paragraph{参考资料和视频。}
有关隧道技术以及如何使用它来绕过防火墙的详细内容，可以在以下章节中找到：

\begin{itemize}
\item 《SEED书》第9章 \seedisbook
\end{itemize}



\paragraph{实验环境。} 
\seedenvironmentB
\nodependency



% *******************************************
% SECTION
% *******************************************
\section{任务0：熟悉实验室设置}

本节将进行一系列实验。
这些实验需要使用两个不同网络中的几台计算机。实验设置如图~\ref{tunnel:fig:network-setup}所示。
我们将使用docker容器模拟这些机器。读者可以从这个实验的网站上找到容器设置文件。
在这个实验中，外部网络 \externalnet 作为外部网络，而内部网络 \internalnet 作为内部网络。

主机 \texttt{10.8.0.1} 不是容器；该 IP 地址分配给宿主机器（即我们的情况中的 VM）。这是互联网的网关。要从两个网络中的主机 \externalnet 和 \internalnet 访问互联网，数据包必须路由到 \texttt{10.8.0.1}。路由已经设置好。

\begin{figure}[htb]
  \begin{center}
    \includegraphics[width=0.9\textwidth]{\tunnelFigs/network-setup.pdf}
  \end{center}
  \caption{网络设置}
  \label{tunnel:fig:network-setup}
\end{figure}

\paragraph{路由器配置：设置NAT。}
路由器配置中包含在 \texttt{docker-compose.yml} 文件内的以下 \texttt{iptables} 命令。
该命令在从其 \texttt{eth0} 接口发出的流量上设置了 NAT，但跳过了到 \externalnet 的数据包。
凭借这条规则，对于要发往互联网的数据包，其源 IP 地址将被替换为路由器的 IP 地址 \texttt{10.8.0.11}。发往 \externalnet 的数据包不会经过 NAT。

\begin{lstlisting}
iptables -t nat -A POSTROUTING ! -d 10.8.0.0/24 -j MASQUERADE -o eth0
\end{lstlisting}

在上述命令中，我们假设 \texttt{eth0} 是连接到外部网络的接口所分配的名字。这并不是一定的。路由器有两个以太网接口；当创建路由器容器时，此接口可能会被命名为 \texttt{eth1}。您可以使用以下命令找出正确的接口名称。
如果名称不是 \texttt{eth0}，您需要在 \texttt{docker-compose.yml} 文件中修改上述命令，并重新启动容器。

\begin{lstlisting}
# ip -br address
lo               UNKNOWN        127.0.0.1/8
eth1@if1907      UP             192.168.20.11/24
(*@\textbf{eth0}@*)@if1909      UP             (*@\textbf{10.8.0.11/24}@*)
\end{lstlisting}

\paragraph{路由器配置：防火墙规则。}
我们还在路由器上添加了以下防火墙规则。
请确保 \texttt{eth0} 是连接到外部网络的接口，而 \texttt{eth1} 连接到内部网络。如果不是这样，请相应地进行修改。

\begin{lstlisting}
// 入站过滤：只允许 SSH 交通
iptables -A FORWARD -i eth0 -p tcp -m conntrack \
         --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -p tcp --dport 22 -j ACCEPT
iptables -A FORWARD -i eth0 -p tcp -j DROP

// 出站过滤：阻止 www.example.com
iptables -A FORWARD -i eth1 -d 93.184.216.0/24 -j DROP
\end{lstlisting}

第一条规定，只有当 TCP 数据包属于已建立或相关连接时才允许它们进入。这是一条状态防火墙规则。第二条规定允许 SSH 会话，第三条规定拒绝所有其他未满足上述条件的 TCP 数据包。
第四条规定是出站防火墙规则，它防止内部主机向 \texttt{93.184.216.0/24} 发送数据包（该网络对应于 \texttt{www.example.com}）。



\paragraph{实验任务。}
请阻塞两个更多网站，并在设置文件中添加防火墙规则。
选择的网站由您自行决定。我们将在其中一个任务中使用这些网站。
请注意，大多数流行网站有多重 IP 地址，这些地址会随着时间变化。添加规则后，请启动容器，
并验证所有入站和出站防火墙规则是否按预期工作。



% *******************************************
% SECTION
% *******************************************
\section{任务1：静态端口转发} 

实验室设置中的防火墙阻止外部机器连接到内部网络上的任何 TCP 服务器，除了 SSH 服务器。在本任务中，我们将使用静态端口转发来绕过这个限制。
更具体地说，我们将使用 \texttt{ssh} 在主机 A（外部网络）和主机 B（内部网络）之间创建一个静态端口转发隧道，
这样接收到 A 的端口 X 的任何数据都将被发送到 B，在那里再将这些数据转发给目标 T 的端口 Y。以下命令用于建立这样的隧道。

\begin{lstlisting}
$ ssh -4NT -L <A's IP>:<A's port X>:<T's IP>:<T's port Y>  <user id>@<B's IP>

// -4: 只使用 IPv4，否则会出现一些错误信息。
// -N: 不执行远程命令。
// -T: 禁用伪终端分配（节省资源）。
\end{lstlisting}
 
关于 A 的 IP 地址，通常我们使用 \texttt{0.0.0.0}，表示我们的端口转发将监听来自 A 所有接口的连接。如果我们想限制来自特定接口的连接，我们可以使用该接口的 IP 地址。例如，如果我们要限制连接到循环回环接口，则只有本地主机上的程序可以使用此端口转发，
我们就可以使用 \texttt{127.0.0.1:<port>} 或简单地省略 IP 地址（默认 IP 地址为 \texttt{127.0.0.1}）。  



\paragraph{实验任务。}
请使用静态端口转发在外部网络和内部网络之间创建一个隧道，以便从主机 \texttt{B1} 进行 telnet 到服务器的操作。
请演示您可以从主机 \texttt{A}、\texttt{A1} 和 \texttt{A2} 这里进行这样的 telnet。此外，请回答以下问题：
(1) 在整个过程中涉及多少个 TCP 连接？您应该运行 wireshark 或 \texttt{tcpdump} 来捕获网络流量，
然后指出从捕获的流量中所有涉及的 TCP 连接。
(2) 为什么这个隧道能够成功帮助用户绕过实验室设置中的防火墙规则？


% *******************************************
% SECTION
% *******************************************
\section{任务2：动态端口转发}

在静态端口转发中，每个端口转发隧道都会将数据转发到特定的目的地。如果我们想向多个目的地转发数据，我们需要建立多个隧道。
例如，使用端口转发我们能够成功访问被封锁的 \texttt{example.com} 网站，但假如防火墙阻止了其他许多网站，
我们该如何避免为每个站点烦琐地建立一个 SSH 隧道？我们可以使用动态端口转发来解决这个问题。



在实验室设置中，路由器已经阻塞了 \texttt{example.com}，因此内部网络中的主机无法访问该网站。请向路由器添加防火墙规则，
以便阻塞两个更多的网站。网站的选择由学生自行决定。请提供证据证明这些网站确实被阻塞。



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务2.1：设置动态端口转发}

我们可以使用 \texttt{ssh} 在主机 \texttt{B} 和主机 \texttt{A} 之间创建一个动态端口转发隧道。我们可以在主机 B 上运行以下命令。
在动态端口转发中，通常将 B 称为代理。  

\begin{lstlisting}
$ ssh -4NT -D <B's IP>:<B's port X>   <user id>@<A's IP>
\end{lstlisting}

关于 B 的 IP 地址，通常我们使用 \texttt{0.0.0.0}，表示我们的端口转发将监听来自 B 所有接口的连接。
建立隧道后，我们可以用 \texttt{curl} 命令来测试它。我们将指定一个代理选项，这样 \texttt{curl} 将会向代理 B 发送 HTTP 请求，
代理 B 会在其上运行 \texttt{X} 端口。代理将接收到的数据转发到隧道的另一端（主机 A），从那里数据将继续被转发给目标网站。
这种代理类型被称为 SOCKS v5，因此我们指定了 \texttt{socks5h}。

\begin{lstlisting}
$ curl --proxy socks5h://<B's IP>:<B's port>  <blocked URL>
\end{lstlisting}

\paragraph{实验任务。}请演示您可以用 \texttt{curl} 从内部网络上的主机 \texttt{B}、\texttt{B1} 和 \texttt{B2} 访问所有被封锁的网站。
此外，请回答以下问题：
(1) 哪台计算机实际与目标服务器建立连接？
(2) 这台计算机是如何知道它应该连接到哪个服务器的？



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务2.2：使用浏览器测试隧道} 

我们也可以通过一个真实的浏览器来测试隧道，而不仅限于用 \texttt{curl}。虽然要在容器内部运行浏览器比较困难，
但在 docker 设置中，默认情况下主机总是会连接到任何在 docker 中创建的网络，并且在该网络上第一个分配给主机的 IP 地址也会被分配给主机。
例如，在我们的设置中，主机是 SEED VM；其在内部网络 \internalnet 上的 IP 地址为 \texttt{192.168.20.1}。

为了使用动态端口转发，
我们需要配置 Firefox 的代理设置。要到达设置页面，可以在 URL 字段内输入 \texttt{about:preferences} 或者点击 \texttt{Preference} 菜单项。
在 \texttt{General} 页面中找到 \texttt{"Network Settings"} 部分，点击 \texttt{Settings} 按钮，一个窗口会弹出。按照图~\ref{tunneling:fig:socks_config}来设置 SOCKS 代理。

\begin{figure}[t]
\begin{center}
   \includegraphics[width=0.9\textwidth]{\tunnelFigs/firefox_proxy.pdf}
\end{center}
\caption{配置 SOCKS 代理}
\label{tunneling:fig:socks_config}
\end{figure}


\paragraph{实验任务。}一旦设置了代理，我们就可以浏览任何网站了。请求和回复将通过 SSH 隧道进行传输。
由于主机虚拟机可以直接访问互联网，为了确保我们的网页浏览流量确实经过了隧道，
您应该执行以下操作：（1）在路由器/防火墙上运行 \texttt{tcpdump}，并指出整个端口转发过程中的所有涉及的流量。  
(2) 断开 SSH 隧道，并尝试浏览一个网站。描述您的观察结果。



\paragraph{清理。}完成此任务后，请务必通过选择“无代理”选项来从 Firefox 中删除代理设置。
否则，未来的实验可能会受到影响。



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务2.3：使用 Python 编写 SOCKS 客户端}

为了使端口转发能够生效，我们需要指定数据应被转发到何处（最终目的地）。在静态情况下，这条信息是在建立隧道时提供的，
也就是说，它被硬编码在了隧道的设置中。而在动态情况下，最终的目的地是动态变化的，并不是在设置时就指定好的。
那么如何让代理知道应该如何将数据进行转发？

使用动态端口转发代理的应用程序必须告诉代理应该向何处转发它们的数据。
这需要应用程序和代理之间的一个额外协议。一种常用的此类用途的协议是 SOCKS (Socket Secure) 协议，它已成为事实上的代理标准。

由于应用程序需要用 SOCKS 协议与代理进行交互，
应用程序软件必须具有原生支持才能使用 SOCKS 代理。Firefox 和 \texttt{curl} 都有这种支持，但 telnet 程序不能直接使用这类代理，
因为它们不提供原生的 SOCKS 支持。在这个任务中，我们将会用 Python 实现一个非常简单的 SOCKS 客户端程序。

\begin{lstlisting}
#!/bin/env python3
import socks

s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "<proxy's IP>", <proxy's port>) 
s.connect(("<server's IP or hostname>", <server's port>))                
hostname = "www.example.com"
req = b"GET / HTTP/1.0\r\nHost: " + hostname.encode('utf-8') + b"\r\n\r\n"
s.sendall(req)
response = s.recv(2048)
while response:
  print(response.split(b"\r\n"))
  response = s.recv(2048)
\end{lstlisting}

\paragraph{实验任务。}请完成此程序，并使用它从主机 \texttt{B}、\texttt{B1} 和 \texttt{B2} 访问 \url{http://www.example.com}。
给定的代码仅用于发送 HTTP 请求，而不是 HTTPS 请求（发送 HTTPS 请求由于 TLS 握手的原因要复杂得多）。对于此任务，
学生只需发送 HTTP 请求。



% *******************************************
% SECTION
% *******************************************
\section{任务3：虚拟专用网络 (VPN)} 

通常使用 VPN 来绕过防火墙。在这个任务中我们将利用 VPN 来绕过入站和出站的防火墙。 
OpenVPN 是一种强大的工具，但我们在这里将仅简单地使用 SSH，这被称为穷人版的 VPN。
我们需要在服务器上更改一些默认的 SSH 设置以允许创建虚拟专用网络。以下是在 \path{/etc/ssh/sshd_config} 中所做的更改，
这些设置已经在容器内部启用。

\begin{lstlisting}
PermitRootLogin yes
PermitTunnel    yes
\end{listing}



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务3.1：绕过入站防火墙}

为了在客户端和服务器之间创建一个虚拟专用网络隧道，我们运行以下 SSH 命令。该命令在客户端和服务器机器上创建了一个 TUN 接口 \texttt{tun0}，
然后使用加密的 TCP 连接将这两个 TUN 接口连接起来。
选项 \texttt{0:0} 中的两个零表示 \texttt{tun0}。关于 \texttt{-w} 选项的详细解释可以在 SSH 的手册中找到。

\begin{lstlisting}
# ssh -w 0:0 root@<VPN Server's IP>
\end{listing}

还应注意，创建 TUN 接口需要具有根权限，因此我们在这两端都需要拥有根权限。这就是为什么我们在 root 账户下运行此命令，并且也在服务器上 SSH 进入 root 账户。
上面的命令仅建立了隧道；两端还需要进一步配置。以下改进后的命令包括了一些配置命令：

\begin{lstlisting}
# ssh -w 0:0 root@<VPN Server's IP> \
      -o "PermitLocalCommand=yes" \
      -o "LocalCommand= ip addr add 192.168.53.88/24 dev tun0 && \
                        ip link set tun0 up" \
      -o "RemoteCommand=ip addr add 192.168.53.99/24 dev tun0 && \
                        ip link set tun0 up"
root@<VPN Server's IP> password: ****  (*@\pointleft{密码：dees}@*)
\end{listing}

\texttt{LocalCommand} 条目指定了在 VPN 客户端上运行的命令。它配置了客户端 TUN 接口：
给接口分配 \texttt{192.168.53.88/24} 地址并激活该接口。
\texttt{RemoteCommand} 条目指定了在 VPN 服务器上运行的命令。
它配置了服务器端 TUN 接口。 
配置不完整，仍然需要进一步配置。

\paragraph{实验任务。}
请在 \texttt{A} 和 \texttt{B} 之间创建一个虚拟专用网络隧道，其中 \texttt{B} 是 VPN 服务器。然后执行必要的所有配置。
一旦设置完成，请演示您可以从外部网络对 \texttt{B}、\texttt{B1} 和 \texttt{B2} 进行 telnet 操作。
请捕获数据包跟踪，并解释为什么这些数据包没有被防火墙阻止。



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务3.2：绕过出站防火墙}

在这个任务中，我们将使用虚拟专用网络来绕过出站的防火墙。在我们的设置中，我们已经阻塞了三个外部网站，
因此内部网络中的主机无法访问这些网站。
本任务的目标是利用隧道技术绕过这些规则。该目标与任务2相同，但这次我们使用的是 VPN 而不是动态端口转发。

创建虚拟专用网络隧道的命令类似于 Task 3.1 中的命令。在这个任务中，我们将 \texttt{B} 作为 VPN 客户端，
\texttt{A} 作为 VPN 服务器。

值得注意的是，在通过隧道将从 VPN 客户端生成的数据包发送到 VPN 服务器时，根据我们的设置，数据包的源 IP 地址将是 \texttt{192.168.53.88}。
当此数据包发出时，它将经过 VirtualBox 的 NAT (网络地址转换) 服务器，在该服务器上，数据包的源 IP 地址将被替换为宿主计算机的 IP 地址。最终，数据包到达 \texttt{example.com}，
然后返回给我们的宿主计算机，并交由同样的 NAT 服务器处理，其目的地地址会被还原为 \texttt{192.168.53.88}。
问题就在这里出现了。

VirtualBox 的 NAT 服务器对 \texttt{192.168.53.0/24} 网络一无所知，因为这是我们在 TUN 接口内部创建的网络，并且 VirtualBox 并不了解如何路由到这个网络，
更不用说知道该数据包应转发给哪个 VPN 服务器。因此，来自 \texttt{example.com} 的回复数据包将被丢弃。

为了解决问题，我们将在VPN服务器上设置自己的NAT服务器。当从\texttt{192.168.53.88}发出的数据包时，它们的源IP地址总是会被替换为
VPN服务器 \texttt{A} 的 IP 地址（\hostA）。我们可以使用以下命令在 \texttt{eth0} 接口上设置一个 NAT 服务器。

\begin{lstlisting}
# iptables -t nat -A POSTROUTING -j MASQUERADE -o eth0
\end{listing}

请在一个虚拟专用网络隧道之间建立 \texttt{B} 和 \texttt{A}，其中 \texttt{A} 是 VPN 服务器。请演示您能否成功使用该虚拟专用网络隧道从主机 \texttt{B}、\texttt{B1} 和 \texttt{B2} 访问被阻塞的网站。
请捕获数据包跟踪，并解释为什么这些数据包没有被防火墙阻止。



% *******************************************
% SECTION
% *******************************************
\section{任务4：SOCKS5 代理与虚拟专用网络 (VPN) 的比较}

SOCKS5 代理（动态端口转发）和虚拟专用网络 (VPN) 是创建隧道以绕过防火墙以及保护通信的常用方法。
许多虚拟专用网络提供商都提供这两种服务。有时，当一家虚拟专用网络提供商告诉您它提供了虚拟专用网络服务时，
实际上它只是一个 SOCKS5 代理。尽管这两种技术都可以用于解决相同的问题，但它们之间存在显著差异。
基于在本实验中的经验，请比较这些两种技术，并描述其不同之处、优缺点。



% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


\end{document}
```